package com.song.myframework.servlet;

import com.alibaba.fastjson.JSONObject;
import com.song.myframework.annotation.MyAutowired;
import com.song.myframework.annotation.MyController;
import com.song.myframework.annotation.MyRequestMapping;
import com.song.myframework.annotation.MyService;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

/**
 * @author Song Yinghui
 * @date 2018/11/2
 * @Version 1.0
 */
public class MyDispatcherServlet extends HttpServlet {

    //web.xml配置的文件名字
    private static final String LOCATION = "centextConfigLocation";

    //读取保存所有配置文件信息
    Properties properties = new Properties();

    //保存所有扫描到的类的全路径
    private List<String> classNames = new ArrayList<>();

    //TODO 保存所有类的对象，目前存储的是类名,实际应为类的全路径（存全路径需考虑 当使用自定义注入对象名称时，需要进行拼接，转换）
    private Map<String, Object> ioc = new HashMap<>();

    //保存所有Url与方法的映射关系
    //todo 需要考虑使用反射获取方法参数与类型再进行invoke
    private Map<String, Method> handleMapping = new HashMap<>();


    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("-----------------------MyFrameWork 初始化开始--------------------");
        //读取配置文件
        doLoadConfig(servletConfig.getInitParameter(LOCATION));

        //扫描所有的类
        doScanner(properties.getProperty("scanPackage"));

        //实例化所有对象（带注解的）
        doInsrance();

        //DI 依赖注入属性
        doAutowired();

        //构造HandleMapping 关系
        initHandleMapping();


        System.out.println("-----------------------MyFrameWork 初始化完成--------------------");
        System.out.println("-----------------------MyFrameWork 初始化数据展示--------------------");
        System.out.println("-----------------------classNames--------------------");
        classNames.forEach(s -> System.out.println(s));

        System.out.println("-----------------------ioc--------------------");
        ioc.forEach((k , v) ->{
            System.out.println(k + "-------"+v.toString());
        });

        System.out.println("-----------------------handleMapping--------------------");
        handleMapping.forEach((k , v) ->{
            System.out.println(k + "-------"+v.toString());
        });
        System.out.println("-----------------------MyFrameWork 初始化数据展示结束--------------------");

    }

    private void initHandleMapping() {

        if (ioc.isEmpty() || ioc.size() == 0) {
            return;
        }

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            if (!clazz.isAnnotationPresent(MyController.class)) {
                continue;
            }

            //获取该类下声明所有方法
            Method[] declaredMethods = clazz.getDeclaredMethods();
            for (Method method : declaredMethods) {
                if (!method.isAnnotationPresent(MyRequestMapping.class)) {
                    continue;
                }

                MyRequestMapping myRequestMapping = method.getAnnotation(MyRequestMapping.class);
                //注解里面的路径
                String pathValue = myRequestMapping.value();
                handleMapping.put(pathValue, method);
            }

        }
    }

    private void doAutowired() {

        if (ioc.size() == 0 || ioc.isEmpty()) {
            return;
        }

        ioc.forEach((k, v) -> {
            try {
                Field[] fields = v.getClass().getDeclaredFields();
                for (Field field : fields) {
                    if (!field.isAnnotationPresent(MyAutowired.class)) {
                        continue;
                    }
                    MyAutowired myAutowired = field.getAnnotation(MyAutowired.class);
                    String beanName = myAutowired.value().trim();
                    //如果自定义对象的话，就应该按自定义的名称注入
                    if ("".equals(beanName)) {
                        beanName = lowerFirstName(field.getType().getSimpleName());
                    }
                    //获取访问权限
                    field.setAccessible(true);
                    field.set(v, ioc.get(beanName));
                }
            } catch (Exception e) {

            }
        });
    }

    private void doInsrance() {
        if (classNames.isEmpty() || classNames.size() == 0) {
            return;
        }

        try {
            for (String str : classNames) {
                Class<?> clazz = Class.forName(str);
                String beanName = "";
                //考虑Service可以自定义对象名称的情况，需要判断
                if (clazz.isAnnotationPresent(MyController.class)) {
                    beanName = lowerFirstName(clazz.getSimpleName());
                    ioc.put(beanName, clazz.newInstance());
                } else if (clazz.isAnnotationPresent(MyService.class)) {
                    MyService annotation = clazz.getAnnotation(MyService.class);
                    String value = annotation.value().trim();
                    if (!"".equals(value)) {
                        beanName = lowerFirstName(clazz.getSimpleName());
                        ioc.put(beanName, clazz.newInstance());
                        continue;
                    }

                    //为空 需要以其实现的接口命名，且默认只实现一个接口，正式环境需判断接口个数
                    Class<?>[] interfaces = clazz.getInterfaces();
                    for (Class<?> c : interfaces) {
                        beanName = lowerFirstName(c.getSimpleName().trim());
                        ioc.put(beanName, clazz.newInstance());
                    }
                } else {
                    //类上没有这两个注解的 暂时不处理
                    continue;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void doScanner(String scanPackage) {

        URL url = this.getClass().getClassLoader().getResource("" + scanPackage.replaceAll("\\.", "/"));
        File file = new File(url.getFile());
        for (File f : file.listFiles()) {
            //如果是文件夹
            if (f.isDirectory()) {
                doScanner(scanPackage + "." + f.getName());
            } else {
                classNames.add(scanPackage + "." + f.getName().replace(".class", "").trim());
            }
        }
    }

    private void doLoadConfig(String initParameter) {
        InputStream in = null;
        try {
            in = this.getClass().getClassLoader().getResourceAsStream(initParameter);
            properties.load(in);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //默认类名采用驼峰命名格式，实例化时将首字母小写
    protected String lowerFirstName(String name) {
        char[] chars = name.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    //运行阶段
    private void doDispacther(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        if (handleMapping.isEmpty() || handleMapping.size() == 0) {
            return;
        }
        String uri = req.getRequestURI();
        String contextPath = req.getContextPath();
        String url = uri.replace(contextPath, "");
        if (!handleMapping.containsKey(url)) {
            resp.getWriter().write("404 , url NOT FOUND");
            return;
        }
        String[] names = req.getParameterValues("name1");
        Map parameterMap = req.getParameterMap();
        Method method = this.handleMapping.get(url);
        String beanName = lowerFirstName(method.getDeclaringClass().getSimpleName());
        try {
            Object name1 = parameterMap.get("name1"); //获得的实际为String[] 需取其中一个
            List<String> objects = JSONObject.parseArray(JSONObject.toJSONString(name1) , String.class);
            //此处是知道参数个数与类型，直接进行invoke的 还需考虑通过反射拿到参数个数与类型，再进行反射
            method.invoke(this.ioc.get(beanName) ,req,resp, objects.get(0));
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doDispacther(req, resp);
    }


}
